/*
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef FLATBUFFERS_REGISTRY_H_
#define FLATBUFFERS_REGISTRY_H_

#include "flatbuffers/idl.h"

namespace flatbuffers {

// Convenience class to easily parse or generate text for arbitrary FlatBuffers.
// Simply pre-populate it with all schema filenames that may be in use, and
// This class will look them up using the file_identifier declared in the
// schema.
class Registry
{
public:
    // Call this for all schemas that may be in use. The identifier has
    // a function in the generated code, e.g. MonsterIdentifier().
    void Register(const char* file_identifier, const char* schema_path)
    {
        Schema schema;
        schema.path_ = schema_path;
        schemas_[file_identifier] = schema;
    }

    // Generate text from an arbitrary FlatBuffer by looking up its
    // file_identifier in the registry.
    bool FlatBufferToText(const uint8_t* flatbuf, size_t len, std::string* dest)
    {
        // Get the identifier out of the buffer.
        // If the buffer is truncated, exit.
        if(len < sizeof(uoffset_t) + FlatBufferBuilder::kFileIdentifierLength)
        {
            lasterror_ = "buffer truncated";
            return false;
        }
        std::string ident(reinterpret_cast<const char*>(flatbuf) + sizeof(uoffset_t),
                          FlatBufferBuilder::kFileIdentifierLength);
        // Load and parse the schema.
        Parser parser;
        if(!LoadSchema(ident, &parser))
            return false;
        // Now we're ready to generate text.
        if(!GenerateText(parser, flatbuf, dest))
        {
            lasterror_ = "unable to generate text for FlatBuffer binary";
            return false;
        }
        return true;
    }

    // Converts a binary buffer to text using one of the schemas in the registry,
    // use the file_identifier to indicate which.
    // If DetachedBuffer::data() is null then parsing failed.
    DetachedBuffer TextToFlatBuffer(const char* text, const char* file_identifier)
    {
        // Load and parse the schema.
        Parser parser;
        if(!LoadSchema(file_identifier, &parser))
            return DetachedBuffer();
        // Parse the text.
        if(!parser.Parse(text))
        {
            lasterror_ = parser.error_;
            return DetachedBuffer();
        }
        // We have a valid FlatBuffer. Detach it from the builder and return.
        return parser.builder_.ReleaseBufferPointer();
    }

    // Modify any parsing / output options used by the other functions.
    void SetOptions(const IDLOptions& opts)
    {
        opts_ = opts;
    }

    // If schemas used contain include statements, call this function for every
    // directory the parser should search them for.
    void AddIncludeDirectory(const char* path)
    {
        include_paths_.push_back(path);
    }

    // Returns a human readable error if any of the above functions fail.
    const std::string& GetLastError()
    {
        return lasterror_;
    }

private:
    bool LoadSchema(const std::string& ident, Parser* parser)
    {
        // Find the schema, if not, exit.
        auto it = schemas_.find(ident);
        if(it == schemas_.end())
        {
            // Don't attach the identifier, since it may not be human readable.
            lasterror_ = "identifier for this buffer not in the registry";
            return false;
        }
        auto& schema = it->second;
        // Load the schema from disk. If not, exit.
        std::string schematext;
        if(!LoadFile(schema.path_.c_str(), false, &schematext))
        {
            lasterror_ = "could not load schema: " + schema.path_;
            return false;
        }
        // Parse schema.
        parser->opts = opts_;
        if(!parser->Parse(schematext.c_str(), vector_data(include_paths_), schema.path_.c_str()))
        {
            lasterror_ = parser->error_;
            return false;
        }
        return true;
    }

    struct Schema
    {
        std::string path_;
        // TODO(wvo) optionally cache schema file or parsed schema here.
    };

    std::string lasterror_;
    IDLOptions opts_;
    std::vector<const char*> include_paths_;
    std::map<std::string, Schema> schemas_;
};

}    // namespace flatbuffers

#endif    // FLATBUFFERS_REGISTRY_H_
